package com.asialjim.wechat.spring.database.impl;

import com.asialjim.wechat.database.DataBaseService;
import com.asialjim.wechat.database.DataBaseTableInfo;
import com.asialjim.wechat.utils.ClassUtils;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * <h1><em>ASIAL JIM JAVA DOC</em></h1><hr/>
 * <h2>CLASS DESCRIPTION <i>[ NAME: DataBaseServiceImpl ]</i></h2><strong>
 * <p>基础数据库服务</p>
 * <p> data base service implements class
 * </strong><p><p><em>Copyright &copy; Asial Jim Co., LTD</em><hr/>
 *
 * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
 * @version 1.0.0
 * @since 2021/1/28   &nbsp;&nbsp; JDK 8
 */
@Slf4j
@Component
@SuppressWarnings({"all"})
public class DataBaseServiceImpl implements DataBaseService, ApplicationContextAware, InitializingBean {
    private ApplicationContext applicationContext;
    private Map<Type, BaseMapper<?>> mapperMap;
    private Map<String,List<DataBaseTableInfo>> tableNameAndProperties;
    private Map<String,Class<?>> tableName2Class;

    @Override
    public Map<String, List<DataBaseTableInfo>> allTables() {
        return tableNameAndProperties;
    }

    @Override
    public <T> IPage<T> selectPage(IPage<T> page, T condition) {
        log.info("\r\n\t从数据库分页查询，查询条件：【{}】，分页条件：【{}】",condition,page);
        BaseMapper<T> pageBaseMapper = (BaseMapper<T>) selectMapper(condition.getClass());
        QueryWrapper<T> wrapper = queryWrapper(condition, TableInfoHelper.getTableInfo(condition.getClass()));
        IPage<T> tiPage = pageBaseMapper.selectPage(page, wrapper);
        log.info("\r\n\t从数据库分页查询，查询条件：【{}】，分页条件：【{}】,结果：【{}】",condition,page,tiPage);
        return tiPage;
    }

    @Override
    public Class<?> classByTableName(String tableName) {
        return tableName2Class.get(tableName);
    }

    @Override
    public List<String> mapperTableNames() {
        return new ArrayList<>(tableNameAndProperties.keySet());
    }

    @Override
    public <T> BaseMapper<T> selectMapper(Class<T> targetClass) {
        return (BaseMapper<T>) mapperMap.get(targetClass);
    }

    @Override
    public <T> int insert(T entity) {
        String tableName = TableInfoHelper.getTableInfo(entity.getClass()).getTableName();
        log.info("\r\n\t向数据库表：{}添加数据：{}", tableName, entity);
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(entity.getClass());
        int insert = baseMapper.insert(entity);
        log.info("\r\n\t向数据库表：{} 添加数据结果代码：{}", tableName, insert);
        return insert;
    }

    @Override
    public <T> int delete(T entity) {
        String tableName = TableInfoHelper.getTableInfo(entity.getClass()).getTableName();
        log.info("\r\n\t删除数据库表：{}实体：{}", tableName, entity);
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(entity.getClass());
        QueryWrapper<T> wrapper = queryWrapper(entity, TableInfoHelper.getTableInfo(entity.getClass()));
        int delete = baseMapper.delete(wrapper);
        log.info("\r\n\t删除数据库表：{}实体结果：{}", tableName, delete);
        return delete;
    }

    @Override
    public <T> int deleteById(Class<T> tableClass, Serializable id) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据主键：{}删除数据库表：{}记录,", id, tableName);
        BaseMapper<T> baseMapper = selectMapper(tableClass);
        int i = baseMapper.deleteById(id);
        log.info("\r\n\t根据主键：{}删除数据库记录结果：{}", tableName, i);
        return i;
    }

    @Override
    public <T> int deleteByMap(Class<T> tableClass, Map<String, Object> columnMap) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据条件：{}删除数据库表:{}记录", columnMap, tableName);
        BaseMapper<T> baseMapper = selectMapper(tableClass);
        int i = baseMapper.deleteByMap(columnMap);
        log.info("\r\n\t根据条件：{}删除数据库表:{}记录结果：{}", columnMap, tableName, i);
        return i;
    }

    @Override
    public <T> int deleteBatchIds(Class<T> tableClass, Collection<? extends Serializable> idList) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据主键：{}批量删除数据库表：{}", idList, tableName);
        int i = selectMapper(tableClass).deleteBatchIds(idList);
        log.info("\r\n\t根据主键：{}批量删除数据库表：{}结果：{}", idList, tableName, i);
        return i;
    }

    @Override
    public <T> int update(T updateToEntity, T updateCondition) {
        String tableName = TableInfoHelper.getTableInfo(updateCondition.getClass()).getTableName();
        log.info("\r\n\t根据条件：{}将数据库表：{}实体跟新为：{}", updateCondition, tableName, updateToEntity);
        UpdateWrapper<T> wrapper = updateWrapper(updateCondition, TableInfoHelper.getTableInfo(updateCondition.getClass()));
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(updateCondition.getClass());
        int update = baseMapper.update(updateToEntity, wrapper);
        log.info("\r\n\t根据条件：{}将数据库表：{}实体更新为：{}的结果为：{}", updateCondition, tableName, updateToEntity, update);
        return update;
    }

    @Override
    public <T> int updateById(T entity) {
        String tableName = TableInfoHelper.getTableInfo(entity.getClass()).getTableName();
        log.info("\r\n\t根据主键更新数据库表：{}实体：{}", tableName, entity);
        int i = ((BaseMapper<T>) selectMapper(entity.getClass())).updateById(entity);
        log.info("\r\n\t根据主键更新数据库表：{}实体：{}结果：{}", tableName, entity, i);
        return i;
    }

    @Override
    public <T> T selectOne(T condition) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(condition.getClass());
        log.info("\r\n\t根据条件：{}查询数据表：{}实体", condition, tableInfo.getTableName());
        QueryWrapper<T> queryWrapper = queryWrapper(condition, tableInfo);
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(condition.getClass());
        T t = baseMapper.selectOne(queryWrapper);
        log.info("\r\n\t根据条件：{}查询到数据库表：{}实体结果为：{}", condition, tableInfo.getTableName(), t);
        return t;
    }

    @Override
    public <T> T selectById(Class<T> tableClass, Serializable id) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据主键：{}从数据库表:{}查询记录", id, tableName);
        T t = selectMapper(tableClass).selectById(id);
        log.info("\r\n\t根据主键：{}从数据库表:{}查询记录结果：{}", id, tableName, t);
        return t;
    }

    @Override
    public <T> List<T> selectBatchIds(Class<T> tableClass, Collection<? extends Serializable> idList) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据主键列表：{}从数据库表:{}查询记录", idList, tableName);
        List<T> ts = selectMapper(tableClass).selectBatchIds(idList);
        log.info("\r\n\t根据主键列表：{}从数据库表:{}查询记录结果：{}", idList, tableName, ts);
        return ts;
    }

    @Override
    public <T> List<T> selectByMap(Class<T> tableClass, Map<String, Object> columnMap) {
        String tableName = TableInfoHelper.getTableInfo(tableClass).getTableName();
        log.info("\r\n\t根据条件：{}从数据库表:{}查询记录", columnMap, tableName);
        List<T> ts = selectMapper(tableClass).selectByMap(columnMap);
        log.info("\r\n\t根据条件：{}从数据库表:{}查询记录结果：{}", columnMap, tableName, ts);
        return ts;
    }

    @Override
    public <T> Integer selectCount(T conditionEntity) {
        String tableName = TableInfoHelper.getTableInfo(conditionEntity.getClass()).getTableName();
        log.info("\r\n\t根据条件：{}从数据库表:{}计数", conditionEntity, tableName);
        QueryWrapper<T> wrapper = queryWrapper(conditionEntity, TableInfoHelper.getTableInfo(conditionEntity.getClass()));
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(conditionEntity.getClass());
        Integer integer = baseMapper.selectCount(wrapper);
        log.info("\r\n\t根据条件：{}从数据库表:{}计数结果：{}", conditionEntity, tableName, integer);
        return integer;
    }

    @Override
    public <T> List<Map<String, Object>> selectMaps(T conditionEntity) {
        String tableName = TableInfoHelper.getTableInfo(conditionEntity.getClass()).getTableName();
        log.info("\r\n\t根据条件：{}从数据库表:{}查询", conditionEntity, tableName);
        QueryWrapper<T> wrapper = queryWrapper(conditionEntity, TableInfoHelper.getTableInfo(conditionEntity.getClass()));
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(conditionEntity.getClass());
        List<Map<String, Object>> maps = baseMapper.selectMaps(wrapper);
        log.info("\r\n\t根据条件：{}从数据库表:{}查询结果：{}", conditionEntity, tableName, maps);
        return maps;
    }

    @Override
    public <T> List<T> selectList(T condition) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(condition.getClass());
        log.info("\r\n\t根据条件：{}查询数据库表：{}实体列表", condition, tableInfo.getTableName());
        QueryWrapper<T> queryWrapper = queryWrapper(condition, tableInfo);
        BaseMapper<T> baseMapper = (BaseMapper<T>) selectMapper(condition.getClass());
        List<T> ts = baseMapper.selectList(queryWrapper);
        log.info("\r\n\t根据条件：{}查询数据库表：{}实体列表结果为：{}", condition, tableInfo.getTableName(), ts);
        return ts;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        mapperMap = new ConcurrentHashMap<>();
        tableNameAndProperties = new ConcurrentHashMap<>();
        tableName2Class = new ConcurrentHashMap<>();

        String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(Repository.class);
        for (String beanName : beanNamesForAnnotation) {
            try {
                BaseMapper<?> bean = applicationContext.getBean(beanName, BaseMapper.class);
                if (Objects.isNull(bean) || !AopUtils.isJdkDynamicProxy(bean)) continue;

                /////////////////////////////////////////获取泛型//////////////////////////////////////////
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(bean);
                AdvisedSupport advised = (AdvisedSupport) new DirectFieldAccessor(invocationHandler).getPropertyValue("advised");
                Class<?>[] proxiedInterfaces = advised.getProxiedInterfaces();          // 获取被代理的接口列表
                if (isEmpty(proxiedInterfaces)) continue;

                Class<?> proxiedInterface = proxiedInterfaces[0];
                Type[] genericInterfaces = proxiedInterface.getGenericInterfaces();     // 获取接口父级接口
                if (isEmpty(genericInterfaces)) continue;

                Type genericInterface = genericInterfaces[0];
                if (!(genericInterface instanceof ParameterizedType)) continue;

                Type[] actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();       // 获取泛型
                if (isEmpty(actualTypeArguments))
                    continue;
                Type actualTypeArgument = actualTypeArguments[0];

                Class<?> aClass = Class.forName(actualTypeArgument.getTypeName());
                if (aClass.isAnnotationPresent(TableName.class)) {
                    TableName annotation = aClass.getAnnotation(TableName.class);

                    var tableInfo = TableInfoHelper.getTableInfo(aClass);
                    var fieldList = tableInfo.getFieldList();
                    var fieldNames = fieldList.stream().map(TableFieldInfo::getField).map(Field::getName).collect(Collectors.toList());
                    var collect = fieldList.stream().map(fieldInfo -> DataBaseTableInfo.builder().propertyName(fieldInfo.getField().getName()).jdbcType(fieldInfo.getJdbcType()).build()).collect(Collectors.toList());

                    tableNameAndProperties.put(annotation.value(), collect);
                    tableName2Class.put(annotation.value(), aClass);
                }
                log.info("\r\n\tsupport table [{}]", tableNameAndProperties);

                mapperMap.put(actualTypeArgument, bean);
                /////////////////////////////////////////获取泛型//////////////////////////////////////////
            } catch (Exception e){
                log.warn("\r\n\tthere is an exception happen:[{}]",e.getMessage());
            }
        }
    }

    /**
     * <h4>METHOD DESCRIPTION</h4>
     * 测试数组是否为空
     *
     * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
     * @since 2021/1/28 14:09
     */
    private boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;
    }

    /**
     * <h4>METHOD DESCRIPTION</h4>
     * 获取查询条件
     *
     * @param condition {@link T 目标数据库表实体记录}
     * @param tableInfo {@link TableInfo 数据库表信息}
     * @return {@link QueryWrapper<T> 查询实体}
     * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
     * @since 2021/1/28 11:59
     */
    private <T> QueryWrapper<T> queryWrapper(T condition, TableInfo tableInfo) {
        Class<?> aClass = condition.getClass();
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();

        Map<String, String> columnMap = new HashMap<>();
        columnMap.put(tableInfo.getKeyProperty(), tableInfo.getKeyColumn());
        tableInfo.getFieldList().forEach(field -> columnMap.put(field.getProperty(), field.getColumn()));

        Arrays.stream(aClass.getDeclaredFields())
                .filter(field -> !field.getName().equals("serialVersionUID"))
                .forEach(field -> putQueryWrapper(condition, queryWrapper, columnMap, field));
        return queryWrapper;
    }

    /**
     * <h4>METHOD DESCRIPTION</h4>
     * 获取更新条件
     *
     * @param updateCondition {@link T 更新条件}
     * @param tableInfo       {@link TableInfo 数据库表信息}
     * @return {@link UpdateWrapper<T> 查询条件}
     * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
     * @since 2021/1/28 12:01
     */
    private <T> UpdateWrapper<T> updateWrapper(T updateCondition, TableInfo tableInfo) {
        Class<?> aClass = updateCondition.getClass();
        UpdateWrapper<T> wrapper = new UpdateWrapper<>();

        Map<String, String> columnMap = new HashMap<>();
        columnMap.put(tableInfo.getKeyProperty(), tableInfo.getKeyColumn());
        tableInfo.getFieldList().forEach(field -> columnMap.put(field.getProperty(), field.getColumn()));

        Arrays.stream(aClass.getDeclaredFields())
                .filter(field -> !field.getName().equals("serialVersionUID"))
                .forEach(field -> putUpdateWrapper(updateCondition, wrapper, columnMap, field));

        return wrapper;
    }

    /**
     * <h4>METHOD DESCRIPTION</h4>
     * 插入查询条件
     *
     * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
     * @since 2021/1/28 12:01
     */
    private <T> void putQueryWrapper(T condition, QueryWrapper<T> queryWrapper, Map<String, String> columnMap, Field field) {
        Object fieldValue = ClassUtils.getFieldValue(condition, field);
        if (Objects.nonNull(fieldValue))
            queryWrapper.eq(columnMap.get(field.getName()), fieldValue);
    }

    /**
     * <h4>METHOD DESCRIPTION</h4>
     * 插入更新条件
     *
     * @author Asial Jim &nbsp;&nbsp; <span>Email:<a href="mailto:asialjim@hotmail.com">asialjim@hotmail.com</a> &nbsp;&nbsp; <a href="asialjim@qq.com">asialjim@qq.com</a></span>
     * @since 2021/1/28 12:02
     */
    private <T> void putUpdateWrapper(T condition, UpdateWrapper<T> updateWrapper, Map<String, String> columnMap, Field field) {
        Object fieldValue = ClassUtils.getFieldValue(condition, field);
        if (Objects.nonNull(fieldValue))
            updateWrapper.eq(columnMap.get(field.getName()), fieldValue);
    }
}